/*
 * $Id$
 */
package corejs;

import static java.lang.Character.*;

/**
 *
 * @author myang
 */
public class JsScanner extends Scanner {

   public JsScanner(final Source src) {
      super(src);
   }

   public JsScanner(final String ... lines) {
      super(new LineReaderSource(new StringsReader(lines)));
   }

   @Override
   protected Token extractToken() {
      skipWhiteSpaces();
      final char c = currentChar();
      if (c == Source.EOF) {
         return Token.EOF;
      }

      final int lineNum = _src.lineNum(), col = _src.col();
      Token tk = null;
      if (isLetter(c)) {
         tk = extractWord();
      } else if (isDigit(c)) {
         tk = extractNumber();
      } else if ('"' == c || '\'' == c) {
         tk = extractString(c);
      } else if (JsTokenType.SPECIAL_SYMBOLS.containsKey(Character.toString(c))) {
         tk = extractSymbol(c);
      } else {
      }
      return tk.lineNum(lineNum).col(1 + col);
   }

   /**
    * 
    */
   private void skipWhiteSpaces() {
      char c = currentChar();
      while (Character.isWhitespace(c)) {
         c = nextChar();
      }
   }

   /**
    * 
    * @return 
    */
   private Token extractWord() {
      final StringBuilder sb = new StringBuilder();
      for (char c = currentChar(); isLetterOrDigit(c); c = nextChar()) {
         sb.append(c);
      }
      final String text = sb.toString();
      final JsTokenType type = JsTokenType.RESERVED_WORDS.get(text);
      return new Token(null == type ? JsTokenType.NAME : type, text);
   }

   /**
    * 
    * @return 
    */
   private Token extractString(final char delimiter) {
      final StringBuilder sb = new StringBuilder();
      for (char c = nextChar(); c != delimiter; c = nextChar()) {
         sb.append(c);
      }
      nextChar();
      return new Token(JsTokenType.STRING, sb.toString());
   }

   /**
    * 
    * @return 
    */
   private Token extractNumber() {
      final String i = extractInteger();
      final char cc = currentChar();
      if (cc == '.') {
         char c = peekChar();
         if (isDigit(c)) {
            nextChar(); // consume .
            final StringBuilder e = 
               new StringBuilder(".").append(extractInteger()); 
            c = currentChar();
            if (c == 'e' || c == 'E') {
               e.append(c); 
               c = nextChar(); // consume e or E
               if (c == '+' || c == '-') {
                  e.append(c); 
                  nextChar(); // consume + or -
               }
               e.append(extractInteger());
            }
            return new Token(JsTokenType.DECIMAL, i + e);
         }
      }
      else if (cc == 'L' || cc == 'l' ) {
         nextChar();
         return new Token(JsTokenType.LONG, i);
      }
      return new Token(JsTokenType.INTEGER, i);
   }

   /**
    * 
    * @return 
    */
   private String extractInteger() {
      final StringBuilder sb = new StringBuilder();
      for (char c = currentChar(); isDigit(c); c = nextChar()) {
         sb.append(c);
      }
      return sb.toString();
   }

   /**
    * 
    * @return 
    */
   private Token extractSymbol(final char c) {
      String s = Character.toString(c);
      switch (c) {
         case '(': case ')': case '[': case ']': case '{': case '}': case ',': 
         case ';': case '%': case ':': case '?':
            nextChar();
            break;

         case '=':
            if ('=' == nextChar()) {
               s = "==";
               if ('=' == nextChar()) {
                  nextChar();
                  s = "===";
               }
            }
            break;

         case '!': 
            if ('=' == nextChar()) {
               s = "!=";
               if ('=' == nextChar()) {
                 nextChar();
                 s = "!==";
               }
            }
            break;

         case '*': case '/': case '^': case '>': case '<':
            if ('=' == nextChar()) {
               nextChar();
               s += '=';
            }
            break;

         case '.':
            if ('.' == nextChar()) {
               nextChar();
               s = "..";
            }
            break;

         case '&': case '|':
            if (c == nextChar()) {
               nextChar();
               s += c;
            }
            break;
      
         case '-': case '+':            
            //final char prevChar = prevChar();
            final char cc = nextChar();
            if (c == cc || cc == '=') { // --, ++, +=, -=
               s += cc;
               nextChar();
            }
            /*else {               
               switch (prevChar) {
                  case ' ': case '[': case '(': case '>': case '<':
                  case '{': case '&': case '=': case '|': case '-':
                  case '+': case ':': case ',': case '%': case '/':
                  if (isDigit(cc)) { // +/- number literal
                     return extractNumber(c);
                  }
                  else if (cc == 'I') { // +/-Infinity
                     
                  }
               }
            }*/
            break;
      }
      final JsTokenType type = JsTokenType.SPECIAL_SYMBOLS.get(s);
      return new Token(type, s);
   }

   /**
    * 
    * @param args 
    */
   public static void main(final String... args) {
      final Scanner s = new JsScanner(
        new LineReaderSource(new StringsReader("var i function 1-2 [-2]", 
        "return col . .. .", "if !== while + += / == *= === ( ) [ ]; ", 
        "\"cool\" yes \"around world\" && || ! < > <= >=", "100 - 345 -20 -890 2L 2l -30L", 
        "90 : yes", "if (a) {i = 100;} else {boo();}", 
        "0.899 100.200 1.2e5 1.25E+10 0.8e-100 100.abc -a - a", 
        "++ -- += -= 1- -20 +30 -2.3 a-10 a -10 a - 10 'of ' 'co\"ol'")));
      for (final Token t : s) {
         System.out.println(t);
      }
      System.out.flush();
   }
}
